Skip to content

Fix sycl::queue leak in usm_ndarray get_queue()/get_device() - #3042

Open
vlad-perevezentsev wants to merge 3 commits into
masterfrom
fix_queue_leak
Open

Fix sycl::queue leak in usm_ndarray get_queue()/get_device()#3042
vlad-perevezentsev wants to merge 3 commits into
masterfrom
fix_queue_leak

Conversation

@vlad-perevezentsev

Copy link
Copy Markdown
Contributor

This PR fixes a memory leak in the C++ accessors usm_ndarray::get_queue() and get_device() in dpnp/include/dpnp4pybind11.hpp

UsmNDArray_GetQueueRef_ (dpnp's usm_ndarray.get_queue_ref()) returns an owning copy of the queue (it calls DPCTLQueue_Copy i.e. new sycl::queue)
Both accessors dereferenced this pointer and copied the queue out but never freed the heap copy, leaking one sycl::queue per call. Under queue churn this keeps queue_impl alive and prevents reuse of the associated Level Zero command lists / event pools.

The fix takes ownership of the returned copy with std::unique_ptr<sycl::queue>.
dpctl needs no change because its Memory_GetQueueRef returns a borrowed ref, whereas dpnp's UsmNDArray_GetQueueRef is documented to return a copy so it must be deleted.

  • Have you provided a meaningful PR description?
  • Have you added a test, reproducer or referred to an issue with a reproducer?
  • Have you tested your changes locally for CPU and GPU devices?
  • Have you made sure that new changes do not introduce compiler warnings?
  • Have you checked performance impact of proposed changes?
  • Have you added documentation for your changes, if necessary?
  • Have you added your changes to the changelog?

@github-actions

Copy link
Copy Markdown
Contributor

View rendered docs @ https://intelpython.github.io/dpnp/pull/3042/index.html

@github-actions

Copy link
Copy Markdown
Contributor

Array API standard conformance tests for dpnp=0.21.0dev6=py314ha0e2e8e_8 ran successfully.
Passed: 1375
Failed: 0
Skipped: 7

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Status

No base build to compare — fix_queue_leak into master

@antonwolfy antonwolfy added this to the 0.21.0 release milestone Aug 26, 2026
// UsmNDArray_GetQueueRef_ returns an owning copy (DPCTLQueue_Copy,
// i.e. `new sycl::queue`); wrap it in a unique_ptr to avoid leaking a
// queue per call.
DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QRef might be NULL on OOM inside DPCTLQueue_Copy, while below dereferencing of NULL is UB.
It seems legacy and can be done in the follow-up PR.

// queue per call.
DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar);
return *(reinterpret_cast<sycl::queue *>(QRef));
std::unique_ptr<sycl::queue> q_ptr{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One point worth considering: freeing via delete (the unique_ptr default deleter) relies on an undocumented implementation detail rather than dpctl's documented ownership contract.

  • DPCTLSyclQueueRef is documented as an opaque pointer, and DPCTLQueue_Copy is annotated __dpctl_give, whose contract is: "the caller now owns the object … to free it, use it exactly once as a value for a __dpctl_take argument" — i.e. DPCTLQueue_Delete.
  • delete happens to be equivalent today only because DPCTLQueue_Delete is literally delete reinterpret_cast<sycl::queue*>(QRef) and dpnp/dpctl share the same runtime/allocator. If dpctl ever changed how it allocates the copy (pool/custom allocator), raw delete would break while DPCTLQueue_Delete would not.

Non-blocking, but to honor the documented contract we could use a unique_ptr with a custom deleter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cleaner option that honors the contract and avoids the link dependency: add a UsmNDArray_RemoveQueueRef capi export that binds DPCTLQueue_Delete, and route the free through the capsule function pointer — mirroring how UsmNDArray_GetQueueRef_ already works, so no consumer needs to link libDPCTLSyclInterface.

dpnp/tensor/_usmarray.pyx:

cdef api void UsmNDArray_RemoveQueueRef(c_dpctl.DPCTLSyclQueueRef QRef):
    """Delete a DPCTLSyclQueueRef previously returned by UsmNDArray_GetQueueRef"""
    c_dpctl.DPCTLQueue_Delete(QRef)   # safe on NULL

dpnp/include/dpnp4pybind11.hpp — add the struct member / ctor-init / assignment mirroring UsmNDArray_GetQueueRef_, then:

DPCTLSyclQueueRef QRef = api.UsmNDArray_GetQueueRef_(raw_ar);
auto qref_deleter = [](sycl::queue *q) {
    detail::dpnp_capi::get().UsmNDArray_RemoveQueueRef_(
        reinterpret_cast<DPCTLSyclQueueRef>(q));
};
std::unique_ptr<sycl::queue, decltype(qref_deleter)> q_ptr{
    reinterpret_cast<sycl::queue *>(QRef), qref_deleter};
return *q_ptr;

This keeps allocation and deallocation on dpctl's side of the ABI and gives a symmetric Get/Remove pair (the capi test would be the natural place for a round-trip check).

That change is non-blocking for the PR — the current delete is provably equivalent under the supported build. So can be also done in the follow-up.

PyUSMArrayObject *raw_ar = usm_array_ptr();

auto const &api = detail::dpnp_capi::get();
// UsmNDArray_GetQueueRef_ returns an owning copy (DPCTLQueue_Copy,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a shorter comment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants